package com.baomibing.flow.service.impl;

import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.baomibing.core.common.Assert;
import com.baomibing.core.common.CollectionMapperDecorator;
import com.baomibing.core.exception.ServerRuntimeException;
import com.baomibing.flow.constant.*;
import com.baomibing.flow.dto.*;
import com.baomibing.flow.enginee.Process;
import com.baomibing.flow.enginee.SubProcess;
import com.baomibing.flow.enginee.TaskJoinProcess;
import com.baomibing.flow.exception.FlowExceptionEnum;
import com.baomibing.flow.execute.Execution;
import com.baomibing.flow.json.JBase;
import com.baomibing.flow.json.JEnd;
import com.baomibing.flow.json.JStart;
import com.baomibing.flow.json.JWorkFlow;
import com.baomibing.flow.model.*;
import com.baomibing.flow.rule.Rule;
import com.baomibing.flow.service.*;
import com.baomibing.flow.track.NodeHistory;
import com.baomibing.flow.track.ProcessTrack;
import com.baomibing.flow.ui.UViewPort;
import com.baomibing.tool.constant.Formats;
import com.baomibing.tool.constant.Strings;
import com.baomibing.tool.util.Checker;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import org.joda.time.DateTime;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.Iterator;
import java.util.List;
import java.util.Map;

/**
 * WorkFlowEngineeImpl
 *
 * @author frog 2023/10/24 15:58
 * @version 1.0.0
 **/
@Service
public class WorkFlowEngineeImpl implements WorkFlowEnginee {

    @Autowired
    private SysFlowService flowService;
    @Autowired private SysFlowInstanceService instanceService;
    @Autowired private SysFlowTaskService taskService;
    @Autowired private SysFlowTrackService trackService;
    @Autowired private SysFlowHistoryInstanceService historyInstanceService;
    @Autowired private SysFlowHistoryTaskService historyTaskService;
    @Autowired private CollectionMapperDecorator collectionMapperDecorator;
    


    @Override
    public SysFlowDto deploy(WorkFlow flow) {
        SysFlowDto xflow;
        JWorkFlow jWorkFlow  = flow.serialize();
        Map<String, JBase> nodeMap = jWorkFlow.getNodeMap();
        String nodes = JSONObject.toJSONString(nodeMap);
        String process = JSONArray.toJSONString(jWorkFlow.getProcesses());
        if (Checker.beEmpty(flow.getId())) {
            xflow = new SysFlowDto();
            xflow.setProcesses(process)
                .setNodes(nodes)
                .setStartNode(JSONObject.toJSONString(nodeMap.get(flow.getStart().getName())))
                .setEndNode(JSONObject.toJSONString(nodeMap.get(flow.getEnd().getName())))
                .setStyle(flow.getStyle()).setViewPort(JSONObject.toJSONString(jWorkFlow.getViewPort()));
        } else {
            xflow = flowService.getFlow(flow.getId());
            xflow.setProcesses(process)
                .setNodes(nodes)
                .setStartNode(JSONObject.toJSONString(nodeMap.get(flow.getStart().getName())))
                .setEndNode(JSONObject.toJSONString(nodeMap.get(flow.getEnd().getName())))
                .setStyle(flow.getStyle()).setViewPort(JSONObject.toJSONString(jWorkFlow.getViewPort()));
        }
        return xflow;
    }

    private String now2String() {
        return new DateTime().toString(Formats.TIME_STAMP_FORMAT);
    }

    @Override
    public String doStartFlow(String flowId, Execution execution) {
       return doStartFlowWithBusinessKey(flowId, Strings.EMPTY, execution);
    }

    @Override
    public String doStartFlowWithBusinessKey(String flowId, String businessKey, Execution execution) {
        SysFlowDto xflow = flowService.getFlow(flowId);
        if (Checker.beNull(xflow)) {
            throw new ServerRuntimeException(FlowExceptionEnum.NOT_FOUND_THE_ID_OF_FLOW, flowId);
        }

        //反序列化生成workflow
        WorkFlow flow = new WorkFlow();
        Map<String, BaseNode> nodeMap = flow.deserialMap(xflow.getNodes());
        List<Process> processes = flow.deserialize(nodeMap, xflow.getProcesses());

        JStart start = JSONObject.parseObject(xflow.getStartNode(), JStart.class);
        JEnd end = JSONObject.parseObject(xflow.getEndNode(), JEnd.class);
        flow.setProcesses(processes)
                .setStart((StartNode) nodeMap.get(start.getName()))
                .setEnd((EndNode) nodeMap.get(end.getName()))
                .setName(xflow.getName()).setStyle(xflow.getStyle())
                .setBeComplete(flow.getBeComplete())
                .setViewPort(JSONObject.parseObject(xflow.getViewPort(), UViewPort.class));

        //执行start
        flow.doStart(execution);

        //序列化工作流
        JWorkFlow jWorkFlow  = flow.serialize();
        String nodes = JSONObject.toJSONString(jWorkFlow.getNodeMap());
        String process = JSONArray.toJSONString(jWorkFlow.getProcesses());
        String tracks = JSONArray.toJSONString(jWorkFlow.getTracks());
        //生成instance
        SysFlowInstanceDto instance = new SysFlowInstanceDto();
        instance.setFlowId(flowId).setBeComplete(flow.getBeComplete()).setName(execution.getActor() + now2String() + flow.getName());
        instance.setProcesses(process).setNodes(nodes).setTracks(tracks)
                .setName(xflow.getName()).setStyle(xflow.getStyle()).setFlowVersion(xflow.getFlowVersion())
                .setStartNode(xflow.getStartNode())
                .setEndNode(xflow.getEndNode()).setBeComplete(flow.getBeComplete()).setViewPort(JSONObject.toJSONString(jWorkFlow.getViewPort()));
        if (Checker.beNotEmpty(businessKey)) {
            instance.setBusinessKey(businessKey);
        }
        instanceService.saveInstance(instance);

        flow.setInstanceId(instance.getId());
        //同时保存历史
        SysFlowHistoryInstanceDto historyInstance = collectionMapperDecorator.map(instance, SysFlowHistoryInstanceDto.class);
        historyInstanceService.saveHistoryInstance(historyInstance);

        doHandleTask(flow);

        return instance.getId();
    }

    @Override
    public void doBindBusinessKey(String instanceId, String businessKey) {
        Assert.CheckArgument(instanceId);
        Assert.CheckArgument(businessKey);
        SysFlowInstanceDto instance = instanceService.getInstance(instanceId);
        if (Checker.beNull(instance)) {
            throw new ServerRuntimeException(FlowExceptionEnum.NOT_FOUND_THE_ID_OF_INSTANCE, instanceId);
        }
        SysFlowInstanceDto newInstance = new SysFlowInstanceDto();
        newInstance.setId(instanceId).setBusinessKey(businessKey);
        instanceService.updateInstance(newInstance);
    }

    private SysFlowTaskDto taskNode2FlowTask(TaskNode node, String instanceId, boolean beChild) {
        SysFlowTaskDto task = new SysFlowTaskDto();
        task.setName(node.getName()).setState(ExecutionTypeEnum.EXECUTING.name())
                .setModel(node.getModel().name()).setNodeId(node.getNodeId())
                .setAllActionRejectType(node.getAllActionRejectType().name())
                .setRecommitPolicy(node.getRecommitPolicy().name())
                .setDirectToTarget(node.getDirectToTarget())
                .setRejectPolicy(node.getRejectPolicy().name())
                .setActionType(node.getActionType().name()).setEntrustType(node.getEntrustType().name())
                .setEntrusts(String.join(Strings.COMMA, node.getEntrusts())).setBeAllActionComplete(node.getBeAllActionComplete())
                .setBeAllActionReject(node.getBeAllActionReject()).setTaskType(node.getTaskType().name())
                .setInstanceId(instanceId).setBeChild(false)
                .setStyle(node.getStyle()).setTaskAdvice(node.getTaskAdvice())
                .setVariables(JSONArray.toJSONString(node.getVariables()));
        //驳回后重新条件策略时，ACTION需要更改
        if (Boolean.FALSE.equals(beChild) && RecommitPolicyEnum.COMMIT_TO_LATEST_REJECT.equals(node.getRecommitPolicy()) && Checker.beNotEmpty(node.getDirectToTarget())) {
            task.setAction(Rule.rule4ActionName(task.getName(), node.getDirectToTarget()));//"ACTON" + "_" + task.getName() + "_" + node.getDirectToTarget());
        } else if  (Checker.beNotEmpty(node.getOutputs())) {
            task.setAction(node.getOutputs().get(0).getName());
        }
        return task;
    }


    private List<SysFlowTaskDto> convert2Tasks(TaskNode node, String instanceId) {
        List<SysFlowTaskDto> list = Lists.newArrayList();
        if (node.getActionType() != ActionTypeEnum.ALL && node.getActionType() != ActionTypeEnum.ALL_ORDER) {
            list.add(taskNode2FlowTask(node, instanceId, false));
        } else {
            TaskJoinProcess childProcess = node.getChildProcess();
            if (Checker.beNull(childProcess)) {
                throw new ServerRuntimeException(FlowExceptionEnum.ALL_OR_ALL_ORDER_TASK_NOT_HAVE_CHILD_PROCESS);
            }
            if (node.getActionType() == ActionTypeEnum.ALL) {
                //审核方式会签
                for (SubProcess p : childProcess.getProcesses()) {
                    if (p.beComplete()) {
                        continue;
                    }
                    SysFlowTaskDto task = taskNode2FlowTask(node, instanceId, true);
                    task.setEntrusts(p.getActor());
                    task.setBeChild(true).setAction(p.getAction().getName());
                    list.add(task);
                }
            } else  {
                //审核方式顺序会签 -- TODO 排序规则
                for (SubProcess p : childProcess.getProcesses()) {
                    if (p.beComplete()) {
                        continue;
                    }
                    SysFlowTaskDto task = taskNode2FlowTask(node, instanceId, true);
                    task.setEntrusts(p.getActor());
                    task.setBeChild(true).setAction(p.getAction().getName());
                    list.add(task);
                    break;
                }
            }
        }
        return  list;
    }

    private void doHandleRejectTask(WorkFlow flow) {
        List<SysFlowTaskDto> executingTasks = taskService.listExecutingTaskByInstance(flow.getInstanceId());
        List<SysFlowTaskDto> completeTasks = Lists.newArrayList();

        List<SysFlowTaskDto> alls = Lists.newArrayList();

        List<NodeHistory> histories = flow.getExecuteOrders();
        Iterator<NodeHistory> historyIterator = histories.iterator();
        while (historyIterator.hasNext()) {
            NodeHistory history = historyIterator.next();
            TaskNode taskNode = history.getTask();
            //更新正在执行的任务
            for (SysFlowTaskDto executingTask : executingTasks) {
                boolean beMatch;
                if (Boolean.TRUE.equals(executingTask.getBeChild())) {
                    beMatch = !Checker.beNull(taskNode.getChildProcess()) && taskNode.getChildProcess().getProcesses().stream()
                            .anyMatch(p -> p.getAction().getName().equals(executingTask.getAction()));
                } else {
                    beMatch = taskNode.getName().equals(executingTask.getName()) && taskNode.getOutputs().get(0).getName().equals(executingTask.getAction());
                }
                if (beMatch) {
                    completeTasks.add(executingTask.setStep(history.getStep()).setExecuteTime(history.getDate()).setActor(history.getActor())
                            .setState(ExecutionTypeEnum.COMPLETED.name()).setExecuteType(history.getExecuteType().name()));
                    historyIterator.remove();
                }
            }

        }

        //保存驳回的完成的任务
        for (NodeHistory history : histories) {
            TaskNode taskNode = history.getTask();
            List<SysFlowTaskDto> tasks = convert2Tasks(taskNode, flow.getInstanceId());
            for (SysFlowTaskDto task : tasks) {
                task.setExecuteType(ProcessExecuteEnum.REJECT.name()).setActor(history.getActor()).setState(ExecutionTypeEnum.COMPLETED.name())
                        .setStep(history.getStep()).setExecuteTime(history.getDate());
            }
            if (Checker.beNotEmpty(tasks)) {
                taskService.saveTasks(tasks);
                alls.addAll(tasks);
            }
        }

        if (Checker.beNotEmpty(completeTasks)) {
            taskService.updateTasks(completeTasks);
            alls.addAll(completeTasks);
        }

        //更新历史
        if (Checker.beNotEmpty(alls)) {
            List<SysFlowHistoryTaskDto> historyTasks = Lists.newArrayList(collectionMapperDecorator.mapCollection(alls, SysFlowHistoryTaskDto.class));
            historyTaskService.saveHistoryTasks(historyTasks);
        }

        //更新正在执行的任务
        if (Checker.beEmpty(flow.getCurrents())) {
            return;
        }

        List<SysFlowTaskDto> flowTaskList = Lists.newArrayList();
        for (WorkNode node : flow.getCurrents()) {
            if (!(node instanceof TaskNode)) {
                continue;
            }
            TaskNode task = (TaskNode) node;
            List<SysFlowTaskDto> tasks = convert2Tasks(task, flow.getInstanceId());
            for (SysFlowTaskDto t : tasks) {
                boolean beHave = executingTasks.stream().anyMatch(e -> e.getAction().equals(t.getAction()));
                if (!beHave) {
                    flowTaskList.add(t);
                }
            }

        }
        if (Checker.beNotEmpty(flowTaskList)) {
            taskService.saveTasks(flowTaskList);
        }


    }

    private void doHandleTask(WorkFlow flow) {
        List<SysFlowTaskDto> executingTasks = taskService.listExecutingTaskByInstance(flow.getInstanceId());

        List<SysFlowTaskDto> completeTasks = Lists.newArrayList();

        List<NodeHistory> histories = flow.getExecuteOrders();
        if (Checker.beNotEmpty(executingTasks)) {
            for (NodeHistory history : histories) {
                Iterator<SysFlowTaskDto> execIt = executingTasks.iterator();
                while (execIt.hasNext()) {
                    SysFlowTaskDto task = execIt.next();
                    if (history.getAction().equals(task.getAction())) {
                        completeTasks.add(task.setStep(history.getStep()).setExecuteTime(history.getDate()).setActor(history.getActor())
                                .setState(ExecutionTypeEnum.COMPLETED.name()).setExecuteType(history.getExecuteType().name()));
                        execIt.remove();
                    }
                }
            }
        }

        if (Checker.beNotEmpty(completeTasks)) {
            taskService.updateTasks(completeTasks);

            //更新历史
            List<SysFlowHistoryTaskDto> historyTasks = Lists.newArrayList(collectionMapperDecorator.mapCollection(completeTasks, SysFlowHistoryTaskDto.class));
            historyTaskService.saveHistoryTasks(historyTasks);
        }

        if (flow.getBeComplete()) {
            taskService.deleteByInstance(flow.getInstanceId());
        }

        if (Checker.beEmpty(flow.getCurrents())) {
            return;
        }

        List<SysFlowTaskDto> flowTaskList = Lists.newArrayList();
        for (WorkNode node : flow.getCurrents()) {
            if (!(node instanceof TaskNode)) {
                continue;
            }
            TaskNode task = (TaskNode) node;
            List<SysFlowTaskDto> tasks = convert2Tasks(task, flow.getInstanceId());
            for (SysFlowTaskDto t : tasks) {
                boolean beHave = executingTasks.stream().anyMatch(e -> e.getAction().equals(t.getAction()));
                if (!beHave) {
                    flowTaskList.add(t);
                }
            }

        }
        if (Checker.beNotEmpty(flowTaskList)) {
            taskService.saveTasks(flowTaskList);
        }
    }

    private SysFlowInstanceDto loadInstance(String instanceId) {
        SysFlowInstanceDto instance = instanceService.getInstance(instanceId);
        if (Checker.beNull(instance)) {
            throw new ServerRuntimeException(FlowExceptionEnum.NOT_FOUND_THE_ID_OF_INSTANCE, instanceId);
        }
        return instance;
    }

    private WorkFlow deserialize(SysFlowInstanceDto instance) {
        WorkFlow flow = new WorkFlow();

        //反序列化
        Map<String, BaseNode> nodeMap = flow.deserialMap(instance.getNodes());
        List<Process> process = flow.deserialize(nodeMap, instance.getProcesses());
        List<ProcessTrack> tracks = flow.deserialTrack(nodeMap, instance.getTracks());
        JStart start = JSONObject.parseObject(instance.getStartNode(), JStart.class);
        JEnd end = JSONObject.parseObject(instance.getEndNode(), JEnd.class);
        flow.setInstanceId(instance.getId()).setTracks(tracks).setProcesses(process).setNodeMap(nodeMap)
                .setStart((StartNode) nodeMap.get(start.getName())).setEnd((EndNode) nodeMap.get(end.getName()))
                .setBeComplete(instance.getBeComplete())
                .setViewPort(Checker.beNotEmpty(instance.getViewPort()) ? JSONObject.parseObject(instance.getViewPort(), UViewPort.class) : null);

        if (instance.getBeComplete()) {
            flow.addCurrents(Lists.newArrayList((EndNode)nodeMap.get(end.getName())));
            flow.withStep(this.historyTaskService.maxStepByInstance(instance.getId()) + 1);
            return flow;
        }

        //加载正在执行的任务
        List<SysFlowTaskDto> executingTasks = taskService.listExecutingTaskByInstance(instance.getId());
        Map<String, TaskNode> executingNodeMap = Maps.newHashMap();
        for (SysFlowTaskDto task : executingTasks) {
            TaskNode taskNode = (TaskNode) flow.getNodeMap().get(task.getName());
            if (Checker.beNull(executingNodeMap.get(task.getName()))) {
                executingNodeMap.put(task.getName(), taskNode);
            }
        }

        flow.addCurrents(Lists.newArrayList(executingNodeMap.values()));
        flow.withStep(historyTaskService.maxStepByInstance(instance.getId()) + 1);

        return flow;

    }

    private void serialize(WorkFlow flow, SysFlowInstanceDto instance) {
        JWorkFlow jWorkFlow  = flow.serialize();
        String nodeJson = JSONObject.toJSONString(jWorkFlow.getNodeMap());
        String processJson = JSONArray.toJSONString(jWorkFlow.getProcesses());
        String trackJson = JSONArray.toJSONString(jWorkFlow.getTracks());

        instance.setNodes(nodeJson).setTracks(trackJson).setProcesses(processJson).setBeComplete(flow.getBeComplete()).setViewPort(JSONObject.toJSONString(jWorkFlow.getViewPort()));
        instanceService.updateInstance(instance);

        //同时保存历史
        SysFlowHistoryInstanceDto historyInstance = collectionMapperDecorator.map(instance, SysFlowHistoryInstanceDto.class);
        historyInstanceService.updateHistoryInstance(historyInstance);
    }

    @Override
    public WorkFlow doExecute(String instanceId, String name, Execution execution) {
        SysFlowInstanceDto instance = loadInstance(instanceId);
        if (instance.getBeComplete()) {
            throw new ServerRuntimeException(FlowExceptionEnum.PROCESS_HAVE_BEEN_COMPLETE);
        }
        WorkFlow flow = deserialize(instance);

        //执行
        flow.doExecute(name, execution);
        flow.doOrdering();
        //序列化更新instance
        serialize(flow, instance);

        //处理任务
        doHandleTask(flow);

        return flow;
    }


    @Override
    public WorkFlow doExecute(String instanceId, Map<String, Execution> executionMap) {

        SysFlowInstanceDto instance = loadInstance(instanceId);
        if (instance.getBeComplete()) {
            throw new ServerRuntimeException(FlowExceptionEnum.PROCESS_HAVE_BEEN_COMPLETE);
        }
        WorkFlow flow = deserialize(instance);

        //执行
        flow.doExecute(executionMap);
        flow.doOrdering();
        //序列化更新instance
        serialize(flow, instance);

        //处理任务
        doHandleTask(flow);

        return flow;
    }



    @Override
    public WorkFlow doReject(String instanceId, String name, Execution execution) {
        SysFlowInstanceDto instance = loadInstance(instanceId);

        WorkFlow flow = deserialize(instance);
        if (instance.getBeComplete()) {
            flow.doExecuteReject(flow.getEnd().getName(), Execution.createByNodeType(NodeTypeEnum.End));
        }

        //执行
        flow.doExecuteReject(name, execution);
        flow.doOrdering();
        //序列化更新instance
        serialize(flow, instance);

        //处理任务
        doHandleRejectTask(flow);

        return flow;
    }

    @Override
    public WorkFlow doReject(String instanceId, Map<String, Execution> executionMap) {
        SysFlowInstanceDto instance = loadInstance(instanceId);

        WorkFlow flow = deserialize(instance);
        if (instance.getBeComplete()) {
            Execution execution = executionMap.get(flow.getEnd().getName());
            if (Checker.beNotNull(execution)) {
                flow.doExecuteReject(flow.getEnd().getName(), execution);
            } else {
                flow.doExecuteReject(flow.getEnd().getName(), Execution.createByNodeType(NodeTypeEnum.End));
            }
        } else {
            //执行
            flow.doExecuteReject(executionMap);
        }
        flow.doOrdering();
        //序列化更新instance
        serialize(flow, instance);

        //处理任务
        doHandleRejectTask(flow);

        return flow;
    }
}
